package roborally.program.command;

import roborally.itemsonboard.Robot;
import roborally.program.CommandManager;
import roborally.program.text.TextProcedure;
import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import be.kuleuven.cs.som.annotate.Raw;

/**
 * A class representing a combined condition, consisting of one or two other conditions and a connecting procedure (and, or, not).
 * 
 * @invar (repeated)
 * 		An ArgumentCommand must have legal arguments, as defined in the method canHaveAsArguments() at all times.
 * 		| this.canHaveAsArguments(this.getArguments())
 * @invar The procedure must be valid, as defined in canHaveAsProcedure()
 * 		| this.canHaveAsConditionCombinationProcedure(this.getProcedure())
 * 
 * @author Molderez Tom, 2de bach Ingenieurswetenschappen Computerwetenschappen-Elektrotechniek and Reniers Jorn, 2de bach Ingenieurswetenschappen Werktuigkunde-Computerwetenschappen
 * @version 3.0
 *
 */
public class ComplexCondition extends Condition {

	/**
	 * Creates a new ComplexCondition that stores the given ConditionCombinationProcedure.
	 * 
	 * @param procedure The procedure describing this complex condition.
	 * @param args The extra arguments that might be needed.
	 * @effect Evokes the constructor of the superclass with the argument args.
	 * 		| super(args)
	 * @post The procedure of the new ComplexCondition will be the given procedure.
	 * 		| (new this).getProcedure() == procedure
	 * @throws IllegalArgumentException (repeated)
	 * 		When the arguments are not valid according to isPossibleArguments().
	 * 		| !isPossibleArguments(arguments)
	 * @throws IllegalArgumentException
	 * 		When the condition procedure is not valid according to canHaveAsConditionCombinationProcedure().
	 * 		| !canHaveAsConditionCombinationProcedure(procedure)
	 * @throws IllegalArgumentException
	 * 		When the ComplexCondition leaving this method would have invalid arguments.
	 * 		| !(new this).canHaveAsArguments(args)
	 */
	public ComplexCondition(ConditionCombinationProcedure procedure, Object... args) throws IllegalArgumentException {
		super(args);
		if (!canHaveAsConditionCombinationProcedure(procedure))
			throw new IllegalArgumentException("Illegal combined condition procedure");
		this.procedure = procedure;
		if (!canHaveAsArguments(args))
			throw new IllegalArgumentException("The arguments are not valid for procedure: " + procedure.toString());
		
	}
	
	/**
	 * Evaluates this condition for the given robot.
	 * 
	 * @param robot The robot for which this condition should be evaluated.
	 * @post The value returned by getReturnValue() of the condition leaving this method will be the value
	 * 			returned by the method evaluate of the procedure of this condition, with the arguments robot and this.getArguments().
	 * 		| (new this).getReturnValue() == this.getProcedure().evaluate(robot, this.getArguments())
	 * @throws IllegalArgumentException (repeated)
	 * 		When the robot is not valid
	 * 		| !canHaveForExecution(robot)
	 */
	@Override
	public void execute(Robot robot) throws IllegalArgumentException {
		if(!canHaveForExecution(robot))
			throw new IllegalArgumentException("Illegal robot for the condition: " + this.getProcedure().toString());
		this.setReturnValue(this.getProcedure().evaluate(robot, this.getArguments())); // the class-invariant will make sure that no exceptions will be thrown.
	}
	
	/**
	 * Returns a ConditionCombinationProcedure that represents this ComplexCondition.
	 */
	@Basic @Raw @Immutable
	public ConditionCombinationProcedure getProcedure(){
		return procedure;
	}
	
	/**
	 * Checks whether this complex condition can have the given combined condition procedure as its procedure.
	 * 
	 * @param procedure The combined condition procedure for which it must be checked.
	 * @return False if the procedure is null.
	 * 		| if procedure == null
	 * 		|	then result == false
	 */
	@Raw
	public boolean canHaveAsConditionCombinationProcedure(ConditionCombinationProcedure procedure){
		return procedure != null;
	}
	
	/**
	 * Stores the procedure that determines how this condition should be evaluated.
	 */
	private final ConditionCombinationProcedure procedure;
	
	/**
	 * Checks whether this complex condition can have this arguments.
	 * 
	 * @return False if this has no valid executionProcedure and this can not have the given arguments according the superclass.
	 * 		| if !this.canHaveAsProcedure(this.getProcedure())
	 * 		|	then if !super.canHaveAsArguments(arguments)
	 * 		|			then result == false
	 * @return If the superclass cannot have the arguments, as defined at super.canHaveAsArguments(), then the result is false.
	 * 		| if !super.canHaveAsArguments(arguments)
	 * 		|	then result == false
	 * @return If the procedure of this condition cannot have the arguments according to 
	 * 			the procedure then the result is false.
	 * 		| if !this.getProcedure().canHaveAsArguments(arguments)
	 * 		|	then result == false
	 */
	@Raw @Override
	public boolean canHaveAsArguments(Object...arguments) {
		if(!this.canHaveAsConditionCombinationProcedure(this.getProcedure()))
			return super.canHaveAsArguments(arguments);
		return super.canHaveAsArguments(arguments) && this.getProcedure().canHaveAsArguments(arguments);
	}
	
	/**
	 * Returns a String representation of this ComplexCondition.
	 * 
	 * @return The result will be a String beginning with an opening bracket, followed by the name of the procedure,
	 * 			 followed by a String-representation of the arguments, and that ends by a closing bracket.
	 * 		| let temp be a new String
	 * 		| for each argument in {this.getArguments()}
	 * 		|	 temp += " " + this.getProcedure().getArgument(value)
	 * 		| result == "(" + this.getProcedure().toString() + temp + ")"
	 */ 
	@Override
	public String write(String leftSpaces) {
		String temp = new String();
		assert canHaveAsArguments(this.getArguments()); //must satisfy its class invariants.
		for(Object value: this.getArguments())
			temp += " " + this.getProcedure().getArgument(value); 
		return "(" + this.getProcedure().toString() + temp + ")"; 
	}
	
	/**
	 * An enum representing a combination of conditions.
	 * These consists of one or two conditions and a conjunction, disjunction or negation.
	 * 
	 * @author Molderez Tom, 2de bach Ingenieurswetenschappen Computerwetenschappen-Elektrotechniek and Reniers Jorn, 2de bach Ingenieurswetenschappen Werktuigkunde-Computerwetenschappen
	 * @version 2.5
	 *
	 */
	public enum ConditionCombinationProcedure implements TextProcedure {
		/**
		 * An and condition will set the return type of the corresponding complex condition only to true if
		 * both arguments evaluate to true. Otherwise it will be set to false after evoking the evaluate method.
		 */
		AND{

			/**
			 * Evaluates this conjunction.
			 * 
			 * @param robot The robot that should execute this program.
			 * @param args An array of object that are extra arguments that may be needed.
			 * @return The result is true if and only if the first conditon and the second condition in args are true.
			 * 		| Condition.class.cast(args[0]).execute(robot);
			 * 		| Condition.class.cast(args[1]).execute(robot);
			 * 		| result == Condition.class.cast(args[0]).getReturnValue() && Condition.class.cast(args[1]).getReturnValue()
			 * @throws IllegalArgumentException (repeated)
			 * 		If the procedure can't have the given parameters or robot as arguments as defined at canHaveAsArguments() and canHaveAsRobot()
			 * 		| !canHaveAsArguments(args) || !canHaveAsRobot(robot)
			 */
			@Override
			public boolean evaluate(Robot robot, Object...args) throws IllegalArgumentException {
				if(!canHaveAsArguments(args) || !canHaveAsRobot(robot))
					throw new IllegalArgumentException("This and-combination of conditions should have an effective robot and two effective conditions");
				Condition.class.cast(args[0]).execute(robot);
				Condition.class.cast(args[1]).execute(robot);
				return Condition.class.cast(args[0]).getReturnValue() && Condition.class.cast(args[1]).getReturnValue();
			}

			/**
			 * Checks whether the given arguments are legal for this combined condition.
			 * 
			 * @param args The extra arguments that might be needed and that must be checked.
			 * @return  True if and only if the definition of canHaveAsArguments() of the superclass (in the enum the 'general' method) would return true,
			 * 			and if the length of the arguments is 2.
			 * 		| result == super.canHaveAsArguments(args) && args.length == 2
			 */
			@Raw @Override
			public boolean canHaveAsArguments(Object...args) {
				return super.canHaveAsArguments(args) && args.length == 2;
			}
			
			/**
			 * Returns a StringPattern that determines a conditionProcedure. Things of the form
			 * <(> <this.toString()> <(> <random char> <)> <(> <random char> <)> <)> with arbitrary 
			 * white characters will be accepted.
			 */
			@Raw @Immutable @Override @Basic
			public String getStringPattern() {
						//Haakje - eventueel wit karakter(s) - andWoord - eventueel wit karakter(s)
				return "^(\\()(\\s*)(" + this.toString() + ")(\\s*)" +
						//2 keer:
						"(" +
							//eventueel wit karakter(s) - haakje - vervolgens: 
							//of eventueel wit karakter(s) - geen haakje - willekeurige karakters haakje - eventueel wit karakter(s) 
							//of wikkekeurige karakters maar geen haakje.
							"(\\s*)(\\()" + "(((\\s*)([^\\(\\)]+)(\\()(.*)(\\))(\\s*))|([^\\(\\)]*))" + "(\\s*)(\\))(\\s*)" +
						//geeft aan een herhaling van 2 keer.
						"){2}" +
						//einde: eventueel wit karakter(s) - haakje	
						"(\\s*)(\\))";
			}

		},
		/**
		 * An and condition will set the return type of the corresponding complex condition only to true if
		 * at least one of both arguments evaluates to true. Otherwise it will be set to false after evoking the evaluate method.
		 */
		OR{

			/**
			 * Evaluates this disjunction.
			 * 
			 * @param robot The robot that should execute this program.
			 * @param args An array of object that are extra arguments that may be needed.
			 * @return The result is true if and only if the first conditon or the second condition in args is true.
			 * 		| Condition.class.cast(args[0]).execute(robot);
			 * 		| Condition.class.cast(args[1]).execute(robot);
			 * 		| result == Condition.class.cast(args[0]).getReturnValue() || Condition.class.cast(args[1]).getReturnValue()
			 * @throws IllegalArgumentException (repeated)
			 * 		If the procedure can't have the given parameters or robot as arguments as defined at canHaveAsArguments() and canHaveAsRobot()
			 * 		| !canHaveAsArguments(args) || !canHaveAsRobot(robot)
			 */
			@Override
			public boolean evaluate(Robot robot, Object...args) throws IllegalArgumentException {
				if(!canHaveAsArguments(args) || !canHaveAsRobot(robot))
					throw new IllegalArgumentException("This or-combination of conditions should have an effective robot and two effective conditions");
				Condition.class.cast(args[0]).execute(robot);
				Condition.class.cast(args[1]).execute(robot);
				return Condition.class.cast(args[0]).getReturnValue() || Condition.class.cast(args[1]).getReturnValue();
			}
			
			/**
			 * Checks whether the given arguments are legal for this combined condition.
			 * 
			 * @param args The extra arguments that might be needed and that must be checked.
			 * @return  True if and only if the definition of canHaveAsArgumets() of the superclass (in the enum the 'general' method) would return true,
			 * 			and if the length of the arguments is 2.
			 * 		| result == super.canHaveAsArguments(args) && args.length == 2
			 */
			@Raw @Override
			public boolean canHaveAsArguments(Object...args) {
				return super.canHaveAsArguments(args) && args.length == 2;
			}

			/**
			 * Returns a StringPattern that determines a conditionProcedure. Things of the form
			 * <(> <this.toString()> <(> <random char> <)> <(> <random char> <)> <)> with arbitrary 
			 * white characters will be accepted.
			 */
			@Raw @Immutable @Override @Basic
			public String getStringPattern() {
				//Haakje - eventueel wit karakter(s) - andWoord - eventueel wit karakter(s)
				return "^(\\()(\\s*)(" + this.toString() + ")(\\s*)" +
						//2 keer:
						"(" +
							//eventueel wit karakter(s) - haakje - vervolgens: of eventueel wit karakter(s) - geen haakje - willekeurige karakters
							//haakje - eventueel wit karakter(s) of wikkekeurige karakters.
							"(\\s*)(\\()" + "(((\\s*)([^\\(\\)]+)(\\()(.*)(\\))(\\s*))|([^\\(\\)]*))" + "(\\s*)(\\))(\\s*)" +
						//geeft aan een herhaling van 2 keer.
						"){2}" +
						//einde: eventueel wit karakter(s) - haakje	
						"(\\s*)(\\))";
			}
		},
		/**
		 * An and condition will set the return type of the corresponding complex condition only to true if
		 * the argument evaluate to false. Otherwise it will be set to false after evoking the evaluate method.
		 */
		NOT{

			/**
			 * Evaluates this negation.
			 * 
			 * @param robot The robot that should execute this program.
			 * @param args An array of object that are extra arguments that may be needed.
			 * @return The result is true if and only if the conditon in args is false.
			 * 		| Condition.class.cast(args[0]).execute(robot);
			 * 		| result == !Condition.class.cast(args[0]).getReturnValue()
			 * @throws IllegalArgumentException (repeated)
			 * 		If the procedure can't have the given parameters or robot as arguments as defined at canHaveAsArguments() and canHaveAsRobot()
			 * 		| !canHaveAsArguments(args) || !canHaveAsRobot(robot)
			 */
			@Override
			public boolean evaluate(Robot robot, Object... args) throws IllegalArgumentException {
				if(!canHaveAsArguments(args) || !canHaveAsRobot(robot))
					throw new IllegalArgumentException("This not-statement should have an effective robot and one effective condition");
				Condition.class.cast(args[0]).execute(robot);
				return !Condition.class.cast(args[0]).getReturnValue();
			}	
			
			/**
			 * Checks whether the given arguments are legal for this combined condition.
			 * 
			 * @param args The extra arguments that might be needed and that must be checked.
			 * @return  True if and only if the definition of canHaveAsArgumets() of the superclass (in the enum the 'general' method) would return true,
			 * 			and if the length of the arguments is 1.
			 * 		| result == super.canHaveAsArguments(args) && args.length == 1
			 */
			@Raw @Override
			public boolean canHaveAsArguments(Object... args) {
				return super.canHaveAsArguments(args) && args.length == 1;
			}

			/**
			 * Returns a StringPattern that determines a conditionProcedure. Things of the form
			 * <(> <this.toString()> <(> <random char> <)> <)> with arbitrary 
			 * white characters will be accepted.
			 */
			@Raw @Immutable @Override @Basic
			public String getStringPattern() {
				//Haakje - eventueel wit karakter(s) - andWoord - eventueel wit karakter(s)
				return "^(\\()(\\s*)(" + this.toString() + ")(\\s*)" +
						//1 keer:
						"(" +
							//eventueel wit karakter(s) - haakje - vervolgens: of eventueel wit karakter(s) - geen haakje - willekeurige karakters
							//haakje - eventueel wit karakter(s) of wikkekeurige karakters.
							"(\\s*)(\\()" + "(((\\s*)([^\\(\\)]+)(\\()(.*)(\\))(\\s*))|([^\\(\\)]*))" + "(\\s*)(\\))(\\s*)" +
						//geeft aan een herhaling van 2 keer.
						"){1}" +
						//einde: eventueel wit karakter(s) - haakje	
						"(\\s*)(\\))";
			}
		};

		/**
		 * Evaluates this combined condition.
		 * 
		 * @param robot The robot that should execute this program.
		 * @param args An array of object that are extra arguments that may be needed.
		 * @throws IllegalArgumentException
		 * 		If the procedure can't have the given parameters or robot as arguments as defined at canHaveAsArguments() and canHaveAsRobot()
		 * 		| !canHaveAsArguments(args) || !canHaveAsRobot(robot)
		 */
		public abstract boolean evaluate(Robot robot, Object... args) throws IllegalArgumentException ;
		
		/**
		 * Checks whether this ComplexCondition can execute itself with the given robot as robot.
		 * 
		 * @param robot The robot for which this ComplexCondition should be evaluated and that must be checked.
		 * @return If robot is null, the result is false.
		 * 		| if robot == null
		 * 		|	then result == false
		 * @return If robot is terminated, the result is false.
		 * 		| if robot.isTerminated()
		 * 		|	then result == false
		 */
		@Raw 
		public boolean canHaveAsRobot(Robot robot) {
			return robot != null && !robot.isTerminated();
		}
		
		/**
		 * Checks whether this procedure can have the given parameters as arguments.
		 * 
		 * @param args The extra arguments that might be needed and that must be checked.
		 * @return If args is null, the result is false.
		 * 		| if args == null
		 * 		|	then result == false
		 * @return If not all objects in args are Conditions, the result is false.
		 * 		| for each object in {args}
		 * 		|	if !Condition.class.isInstance(object)
		 * 		|		then result == false
		 */
		@Raw 
		public boolean canHaveAsArguments(Object... args) {
			if(args == null)
				return false;
			for(Object lusArg : args)
				if(!Condition.class.isInstance(lusArg))
					return false;
			return true;
		}
		
		/**
		 * Returns an Object of witch the given string is the String-representation 
		 * if this condition could have the Object according to argument as parameter of evaluate().
		 * 
		 * @param argument The String of which the corresponding object must be returned.
		 * @return If the argument could be a parameter of the method evaluate() according to containsArgument()
		 * 			then the result is the object of which the given String is a representation.
		 * 		| if containsArgument(argument)
		 * 		|	then result == Condition.class.cast(CommandManager.get().createCommand(argument))
		 * @throws IllegalArgumentException (repeated)
		 * 		When the command can not contain the given String argument as parameter.
		 * 		| !containsArgument(argument)
		 * @note The meaning of the couple value (Object) and argument (String) is the following:
		 * If you would like to give the value (object) as an argument for this condition, than you
		 * should give the argument (String) as parameter in the program.
		 */
		@Raw @Override
		public Object getValue(String argument) throws IllegalArgumentException {
			try {
				return Condition.class.cast(CommandManager.get().createCommand(argument));
			} catch (ClassCastException exc) {
				throw new IllegalArgumentException("Complex condition can not have the given string as argument representation: " + argument);
			}
		}
		
		/**
		 * Returns a String representation of the given object if this procedure could contain the object.
		 * 
		 * @param value The value (Object) of which the corresponding argument (String) must be returned.
		 * @return The String representation of this value if valid.
		 * 		| if containsValue(value)
		 * 		|	then result == Condition.class.cast(value).write("")
		 * @throws IllegalArgumentException (repeated)
		 * 		When the value is not a valid argument
		 * 		| !containsValue(value)
		 * @note The meaning of the couple value (Object) and argument (String) is the following:
		 * If you would like to give the value (object) as an argument for this condition, than you
		 * should give the argument (String) as parameter in the program.
		 */
		@Raw @Override
		public String getArgument(Object value) {
			if(containsValue(value))
				return Condition.class.cast(value).write("");
			else
				throw new IllegalArgumentException("Complex condition can not have the argument: " + value.toString());
		}
		
		/**
		 * Returns whether this condition could have the given value as parameter.
		 * 
		 * @return The result is true if value is a Condition.
		 * 		| result == Condition.class.isInstance(value)
		 */
		@Raw @Override
		public boolean containsValue(Object value) {
			return Condition.class.isInstance(value);
		}
		
		/**
		 * Returns whether this condition could have the Object according to argument as parameter.
		 * 
		 * @return  The result is true if argument is a String representing a Condition.
		 * 		| if Condition.class.isInstance(CommandManager.get().createCommand(argument))
		 * 		|	then result == true
		 */
		@Raw @Override
		public boolean containsArgument(String argument) {
			try  {
				Condition.class.cast(CommandManager.get().createCommand(argument));
				return true;
			} catch(IllegalArgumentException exc) {
				return false;
			} catch(ClassCastException exc) {
				return false;
			}
		}

		/**
		 * Returns a textual representation of this combined condition.
		 * 
		 * @return Returns a String equal to the enum-literal but with lower cases.
		 * 		| super.toString().toLowerCase()_
		 */
		@Raw @Override
		public final String toString() {
			return super.toString().toLowerCase().replaceAll("_", "-");
		}
	}

}
